/**
 * @author: dn-jinmin/dn-jinmin
 * @doc: 该文件实现了WebSocket客户端的功能，包括连接、发送和接收消息的能力。
 */

package websocket

import (
	"encoding/json"
	"github.com/gorilla/websocket"
	"net/url"
)

// Client 定义了WebSocket客户端的接口，提供了关闭连接、发送和接收消息的方法。
type Client interface {
	Close() error

	Send(v any) error
	Read(v any) error
}

// client 是WebSocket客户端的具体实现。
type client struct {
	*websocket.Conn
	host string

	opt dailOption
}

// NewClient 创建并返回一个新的WebSocket客户端实例。
// 参数host是WebSocket服务器的地址。
// opts是可变参数，用于配置客户端的连接选项。
func NewClient(host string, opts ...DailOptions) *client {
	opt := newDailOptions(opts...)

	c := client{
		Conn: nil,
		host: host,
		opt:  opt,
	}

	conn, err := c.dail()
	if err != nil {
		panic(err)
	}

	c.Conn = conn
	return &c
}

// dail 尝试根据当前配置建立一个WebSocket连接。
// 返回建立的WebSocket连接对象，或者遇到的错误。
func (c *client) dail() (*websocket.Conn, error) {
	u := url.URL{Scheme: "ws", Host: c.host, Path: c.opt.pattern}
	conn, _, err := websocket.DefaultDialer.Dial(u.String(), c.opt.header)
	return conn, err
}

// Send 将一个值序列化为JSON格式，并通过WebSocket连接发送。
// 如果发送失败，尝试重新连接并再次发送。
// 返回发送过程中遇到的错误，如果没有错误，则返回nil。
func (c *client) Send(v any) error {
	data, err := json.Marshal(v)
	if err != nil {
		return err
	}
	err = c.WriteMessage(websocket.TextMessage, data)
	if err == nil {
		return nil
	}
	// todo: 再增加一个重连发送
	conn, err := c.dail()
	if err != nil {
		return err
	}
	c.Conn = conn
	return c.WriteMessage(websocket.TextMessage, data)
}

// Read 从WebSocket连接读取一个消息，并将其反序列化为指定的值。
// 返回读取和反序列化过程中遇到的错误，如果没有错误，则返回nil。
func (c *client) Read(v any) error {
	_, msg, err := c.Conn.ReadMessage()
	if err != nil {
		return err
	}

	return json.Unmarshal(msg, v)
}
